package com.ruoyi.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.amazonaws.Protocol;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.oss.constant.OssConstant;
import com.ruoyi.system.domain.ArchiveLocation;
import com.ruoyi.system.domain.ArchiveStrategy;
import com.ruoyi.system.domain.ArchiveTask;
import com.ruoyi.system.enums.ArchiveJobExecuteStatusEnum;
import com.ruoyi.system.enums.ArchiveLocationTypeEnum;
import com.ruoyi.system.enums.ArchiveStrategyTypeEnum;
import com.ruoyi.system.mapper.ArchiveLocationMapper;
import com.ruoyi.system.mapper.ArchiveStrategyMapper;
import com.ruoyi.system.mapper.ArchiveTaskMapper;
import com.ruoyi.system.util.ArchiveUtil;
import com.ruoyi.system.util.Constants;
import com.ruoyi.system.util.NetUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.bo.ArchiveResultBo;
import com.ruoyi.system.domain.vo.ArchiveResultVo;
import com.ruoyi.system.domain.ArchiveResult;
import com.ruoyi.system.mapper.ArchiveResultMapper;
import com.ruoyi.system.service.IArchiveResultService;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;

/**
 * 归档任务执行日志Service业务层处理
 *
 * @author ruoyi
 * @date 2024-12-06
 */
@RequiredArgsConstructor
@Service
public class ArchiveResultServiceImpl implements IArchiveResultService {

    private final ArchiveResultMapper baseMapper;

    private final ArchiveStrategyMapper strategyMapper;

    private final ArchiveTaskMapper archiveTaskMapper;

    private final ArchiveLocationMapper locationMapper;

    /**
     * 查询归档任务执行日志
     */
    @Override
    public ArchiveResultVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 查询归档任务执行日志列表
     */
    @Override
    public TableDataInfo<ArchiveResultVo> queryPageList(ArchiveResultBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ArchiveResult> lqw = buildQueryWrapper(bo);
        Page<ArchiveResultVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询归档任务执行日志列表
     */
    @Override
    public List<ArchiveResultVo> queryList(ArchiveResultBo bo) {
        LambdaQueryWrapper<ArchiveResult> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<ArchiveResult> buildQueryWrapper(ArchiveResultBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<ArchiveResult> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getTaskName()), ArchiveResult::getTaskName, bo.getTaskName());
        lqw.between(params.get("beginStartTime") != null && params.get("endStartTime") != null,
            ArchiveResult::getStartTime, params.get("beginStartTime"), params.get("endStartTime"));
        lqw.between(params.get("beginEndTime") != null && params.get("endEndTime") != null,
            ArchiveResult::getEndTime, params.get("beginEndTime"), params.get("endEndTime"));
        lqw.eq(bo.getExecuteStatus() != null, ArchiveResult::getExecuteStatus, bo.getExecuteStatus());
        return lqw;
    }

    /**
     * 新增归档任务执行日志
     */
    @Override
    public Boolean insertByBo(ArchiveResultBo bo) {
        ArchiveResult add = BeanUtil.toBean(bo, ArchiveResult.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改归档任务执行日志
     */
    @Override
    public Boolean updateByBo(ArchiveResultBo bo) {
        ArchiveResult update = BeanUtil.toBean(bo, ArchiveResult.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(ArchiveResult entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 批量删除归档任务执行日志
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    @Override
    public List<ArchiveResult> queryListForRecover(String taskName) {
        return baseMapper.queryListForRecover(taskName);
    }

    @Override
    public void download(Long id, HttpServletResponse response) throws Exception {
        ArchiveResult archiveResult = baseMapper.selectById(id);
        if (archiveResult == null) {
            throw new ServiceException("归档结果不存在");
        }
        if (ArchiveJobExecuteStatusEnum.SUCCESS.getValue() != archiveResult.getExecuteStatus()) {
            throw new ServiceException("归档结果不是成功状态，不能导出");
        }
        ArchiveTask archiveTask = archiveTaskMapper.selectById(archiveResult.getTaskId());
        if (archiveTask == null) {
            throw new ServiceException("归档结果的归档任务不存在，不能导出");
        }
        ArchiveStrategy archiveStrategy = strategyMapper.selectById(archiveTask.getStrategyId());
        if (archiveStrategy == null) {
            throw new ServiceException("归档任务的归档策略不存在，不能导出");
        }
        ArchiveLocation archiveLocation = locationMapper.selectById(archiveStrategy.getLocationId());
        if (archiveLocation == null) {
            throw new ServiceException("归档策略的归档位置不存在，不能导出");
        }
        download(archiveResult, archiveStrategy, archiveLocation, response);
    }

    private void download(ArchiveResult archiveResult, ArchiveStrategy archiveStrategy,
                          ArchiveLocation archiveLocation, HttpServletResponse response) throws Exception {
        String fileName = String.format("%s_%s.sql", archiveStrategy.getSourceTable(), archiveResult.getResultNo());
        setDownloadHeader(fileName, response);
        if (ArchiveStrategyTypeEnum.COMPRESS_TABLE.getValue() == archiveStrategy.getStrategyType()
            && ArchiveLocationTypeEnum.DB.getValue() == archiveLocation.getLocationType()) {
            downloadFromDb(archiveResult, archiveStrategy, archiveLocation, response);
        } else if (ArchiveStrategyTypeEnum.SQL_FILE.getValue() == archiveStrategy.getStrategyType()) {
            if (ArchiveLocationTypeEnum.LOCAL.getValue() == archiveLocation.getLocationType()) {
                downloadFromSqlLocal(archiveResult, archiveStrategy, archiveLocation, response);
            } else if (ArchiveLocationTypeEnum.S3.getValue() == archiveLocation.getLocationType()) {
                downloadFromSqlS3(archiveResult, archiveLocation, response);
            } else {
                throw new ServiceException("导出失败，要导出的归档位置错误");
            }
        } else {
            throw new ServiceException("导出失败，要导出的归档策略类型错误");
        }
    }

    private void downloadFromSqlS3(ArchiveResult archiveResult, ArchiveLocation archiveLocation,
                                   HttpServletResponse response) throws Exception {
        String s3Url = ArchiveUtil.getS3FilePath(archiveResult, archiveLocation);
        NetUtil.downloadFile(s3Url, response);
    }

    private void downloadFromDb(ArchiveResult archiveResult, ArchiveStrategy archiveStrategy,
                                ArchiveLocation archiveLocation, HttpServletResponse response) throws Exception {
        List<String> fromCells = Arrays.asList(archiveStrategy.getTargetFields().split(Constants.COMMA));
        List<String> toCells = Arrays.asList(archiveStrategy.getSourceFields().split(Constants.COMMA));
        String minPk = archiveResult.getMinPk();
        String maxPk = archiveResult.getMaxPk();
        try (Connection connection = ArchiveUtil.getDbConnection(archiveLocation)) {
            String tmpSql = ArchiveUtil.getSelectSql(archiveStrategy.getTargetTable(), fromCells, archiveStrategy.getTargetPk(), minPk);
            List<Map<String, Object>> temp = ArchiveUtil.queryDb(connection, tmpSql, fromCells);
            if (temp.isEmpty()) {
                throw new ServiceException("导出失败，要恢复的数据在备份库中不存在");
            }
            try (PrintWriter printWriter = response.getWriter()) {
                String sqlHeader = ArchiveUtil.getInsertHeadSql(archiveStrategy.getSourceTable(), toCells);
                List<String> sqlValues = ArchiveUtil.getDataSqlLineList(temp, toCells);
                downloadFromDb(sqlHeader, sqlValues, printWriter);
                minPk = temp.get(0).get(archiveStrategy.getTargetPk()).toString();
                while (ArchiveUtil.pkCompareTo(minPk, maxPk) < 0) {
                    tmpSql = ArchiveUtil.getSelectSql(archiveStrategy.getTargetTable(), fromCells, archiveStrategy.getTargetPk(), minPk, maxPk);
                    if (CollectionUtils.isNotEmpty(temp = ArchiveUtil.queryDb(connection, tmpSql, fromCells))) {
                        printWriter.write(",\n");
                        minPk = temp.get(temp.size() - 1).get(archiveStrategy.getSourcePk()).toString();
                        sqlValues = ArchiveUtil.getDataSqlLineList(temp, toCells);
                        downloadFromDb(sqlValues, printWriter);
                    } else {
                        break;
                    }
                }
            }
        }
    }

    private void downloadFromDb(String sqlHeader, List<String> values, PrintWriter printWriter) {
        String sql = sqlHeader + StringUtils.join(values, "");
        if (sql.endsWith(Constants.COMMA)) {
            sql = sql.substring(0, sql.length() - 1);
        }
        printWriter.write(sql);
    }

    private void downloadFromDb(List<String> values, PrintWriter printWriter) {
        String sql = StringUtils.join(values, "");
        if (sql.endsWith(Constants.COMMA)) {
            sql = sql.substring(0, sql.length() - 1);
        }
        printWriter.write(sql);
    }

    private void downloadFromSqlLocal(ArchiveResult archiveResult, ArchiveStrategy archiveStrategy,
                                      ArchiveLocation archiveLocation, HttpServletResponse response) throws Exception {
        File sqlFile = new File(String.format("%s/archive/%d/%s.sql",
            archiveLocation.getLocalDir(), archiveResult.getTaskId(), archiveResult.getResultNo()));
        if (!sqlFile.exists()) {
            throw new ServiceException("导出失败，要导出的SQL文件不存在");
        }
        downloadFromSqlLocal(sqlFile, response);
    }

    private void downloadFromSqlLocal(File file, HttpServletResponse response) throws Exception {
        try (InputStream inputStream = new FileInputStream(file)) {
            byte[] buffer = new byte[1024];
            int len;
            try {
                while ((len = inputStream.read(buffer)) > 0) {
                    response.getOutputStream().write(buffer, 0, len);
                }
            } catch (IOException e) {
                throw new ServiceException("导出失败:" + e.getMessage());
            }
        }
    }

    public void setDownloadHeader(String fileName, HttpServletResponse response) {
        response.reset();
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.addHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
    }
}
